#include "TcpSocket.h"

#include <memory>

class TcpSocketPrivateData
{
public:
    uv_loop_t *loop{};
    uv_tcp_t *tcpSocket{};
    uv_connect_t *connect{};
    std::string ip;
    int port{};
    std::string lastErrorMessage;

    //回调函数及其数据
    TcpSocket::OnConnect onConnect{};
    void *onConnectData{};
    TcpSocket::OnRead onRead{};
    void *onReadData{};
    TcpSocket::OnReadError onReadError{};
    void *onReadErrorData{};
    TcpSocket::OnClose onClose{};
    void *onCloseData{};
    TcpSocket::OnDisconnected onDisconnected{};
    void *onDisconnectedData{};
};

TcpSocket::TcpSocket()
{
    data = std::unique_ptr<TcpSocketPrivateData>(new TcpSocketPrivateData());
    initializeData();
}

TcpSocket::TcpSocket(uv_tcp_t *clientSocket)
{
    data = std::unique_ptr<TcpSocketPrivateData>(new TcpSocketPrivateData());
    clientSocket->data = (void *) this;
    data->tcpSocket = clientSocket;
    uv_read_start(reinterpret_cast<uv_stream_t *>(clientSocket), TcpSocket::allocBufferCB, TcpSocket::readCB);
}

TcpSocket::~TcpSocket()
{
    if (data->tcpSocket)
    {
        free(data->tcpSocket);
    }
    if (data->connect)
    {
        free(data->connect);
    }
}

void TcpSocket::setEventLoop(uv_loop_t *loop)
{
    data->loop = loop;
}

uv_loop_t *TcpSocket::getLoop() const
{
    return data->loop;
}

void TcpSocket::setIPAddress(const std::string &ip)
{
    data->ip = ip;
}

std::string TcpSocket::getIpAddress() const
{
    return data->ip;
}

void TcpSocket::setPort(int port)
{
    data->port = port;
}

int TcpSocket::getPort() const
{
    return data->port;
}

bool TcpSocket::connectToHost()
{
    struct sockaddr_in addr{};
    uv_ip4_addr(data->ip.c_str(), data->port, &addr);

    data->connect->data = (void *) this;
    uv_tcp_init(data->loop, data->tcpSocket);
    int res = uv_tcp_connect(data->connect, data->tcpSocket, (const struct sockaddr *) &addr, TcpSocket::onConnectCB);
    if (0 != res)
    {
        data->lastErrorMessage = getErrorMessage(res);
    }
    return (0 == res);
}

void TcpSocket::close()
{
    if (data->connect && data->connect->handle)
    {
        uv_close((uv_handle_t *) data->connect->handle, TcpSocket::onCloseCB);
    }
}

void TcpSocket::onConnect(bool status)
{
}

void TcpSocket::read(const char *buf, size_t size)
{
}

void TcpSocket::readError(const char *errorMessage)
{

}

void TcpSocket::disconnected()
{
}

void TcpSocket::onClose()
{

}

void TcpSocket::initializeData()
{
    data->loop = uv_default_loop();
    data->tcpSocket = (uv_tcp_t *) malloc(sizeof(uv_tcp_t));
    data->connect = (uv_connect_t *) malloc(sizeof(uv_connect_t));
}

void TcpSocket::onConnectCB(uv_connect_t *req, int status)
{
    auto *thisPoint = (TcpSocket *) req->data;
    if (status < 0)
    {
        fprintf(stderr, "Connection error, %s\n", TcpSocket::getErrorMessage(status).c_str());
    }
    else
    {
        uv_read_start(req->handle, TcpSocket::allocBufferCB, TcpSocket::readCB);
    }
    req->handle->data = thisPoint;
    if (0 != status)
    {
        thisPoint->data->lastErrorMessage = getErrorMessage(status);
    }
    bool isPass = (0 == status);
    thisPoint->onConnect(isPass);
    if (thisPoint->data->onConnect)
    {
        thisPoint->data->onConnect(isPass, thisPoint->data->onConnectData);
    }
}

void TcpSocket::allocBufferCB(uv_handle_t *handle, size_t suggested_size, uv_buf_t *buf)
{
    buf->len = suggested_size;
    buf->base = static_cast<char *>(malloc(suggested_size));
}

void TcpSocket::readCB(uv_stream_t *client, ssize_t nread, const uv_buf_t *buf)
{
    auto *thisPoint = (TcpSocket *) client->data;
    if (nread > 0)
    {
        thisPoint->read(buf->base, nread);
        if (thisPoint->data->onRead)
        {
            thisPoint->data->onRead(buf->base, nread, thisPoint->data->onReadData);
        }
        free(buf->base);
        return;
    }
    free(buf->base);
    if (UV_EOF != nread)
    {
        const char *errorMessage = getErrorMessage(nread).c_str();
        thisPoint->readError(errorMessage);
        if (thisPoint->data->onReadError)
        {
            thisPoint->data->onReadError(errorMessage, thisPoint->data->onReadErrorData);
        }
        uv_close((uv_handle_t *) client, nullptr);
    }
    else
    {
        thisPoint->disconnected();
        if (thisPoint->data->onDisconnected)
        {
            thisPoint->data->onDisconnected(thisPoint->data->onDisconnectedData);
        }
        uv_close((uv_handle_t *) client, nullptr);
    }
}

void TcpSocket::onCloseCB(uv_handle_t *handle)
{
    auto *thisPoint = (TcpSocket *) handle->data;
    if (nullptr != handle)
    {
        free(handle);
        handle = nullptr;
    }
    thisPoint->onClose();
    if (thisPoint->data->onClose)
    {
        thisPoint->data->onClose(thisPoint->data->onCloseData);
    }
}

std::string TcpSocket::getErrorMessage(int errorCode)
{
    const char *errName = uv_strerror(errorCode);
    return std::string(errName);
}

std::string TcpSocket::getLastErrorMessage() const
{
    return data->lastErrorMessage;
}

void TcpSocket::setOnConnectCB(TcpSocket::OnConnect onConnect, void *userData)
{
    data->onConnect = onConnect;
    data->onConnectData = userData;
}

void TcpSocket::setOnReadCB(TcpSocket::OnRead onRead, void *userData)
{
    data->onRead = onRead;
    data->onReadData = userData;
}

void TcpSocket::setReadErrorCB(TcpSocket::OnReadError readError, void *userData)
{
    data->onReadError = readError;
    data->onReadErrorData = userData;
}

void TcpSocket::setOnCloseCB(TcpSocket::OnClose onClose, void *userData)
{
    data->onClose = onClose;
    data->onCloseData = userData;
}

void TcpSocket::setDisconnectedCB(TcpSocket::OnDisconnected disconnected, void *userData)
{
    data->onDisconnected = disconnected;
    data->onDisconnectedData = userData;
}

int TcpSocket::peerPort() const
{
    sockaddr sockaddr;
    int nameLen = sizeof(sockaddr);
    uv_tcp_getpeername(data->tcpSocket, &sockaddr, &nameLen);

    auto *checkAddr = (sockaddr_in *) &sockaddr;

    return ntohs(checkAddr->sin_port);
}

std::string TcpSocket::peerIp() const
{
    sockaddr sockaddr;
    int nameLen = sizeof(sockaddr);
    uv_tcp_getpeername(data->tcpSocket, &sockaddr, &nameLen);

    char check_ip[17];

    uv_ip4_name((sockaddr_in *) &sockaddr, (char *) check_ip, sizeof(check_ip));

    return std::string(check_ip);
}
void TcpSocket::exec()
{
    uv_run(uv_default_loop(), UV_RUN_DEFAULT);
}

